using Core;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

public class BundleWXTool
{
    public const string RES_ROOT = "Proj/Res"; // 资源根路径

    public static int BucketSize = 1000 * 1024;

    public static string[] UniPath = new string[]
    {
        "Assets/Proj/Res/Shader",
        "Assets/Proj/Res/Conf",
    };

    [Serializable]
    public class AssetItem
    {
        public string Path;

        public string Bundle;

        //依赖项
        public List<string> Relies = new();

        //被依赖项
        public List<string> BeRelies = new();
    }

    [Serializable]
    public class BundleItem
    {
        public string Name;

        public string MD5;

        public int Offset;
        public int Position;
        public int Length;

        public List<string> Assets = new();
    }

    [Serializable]
    public class JsonObj
    {
        public List<AssetItem> Assets = new();
        public List<BundleItem> Bundles = new();
    }

    private static readonly Dictionary<string, AssetItem> DicA = new();
    private static readonly Dictionary<string, BundleItem> DicB = new();

    //[MenuItem("Mini Tools/Bundle/Build(WX)", false)]
    //[MenuItem("Assets/Mini Tools/Bundle/Build(WX)", false)]
    public static void AutoBuild()
    {
        StatisticAssets();

        StatisticBundles();

        ClearAllAssetBundleName();
        MarkAllAssetBundleName();
        BuildAssetBundle();
        GatherBundleMd5();
        //AssignBucket();
        Encrypt();
        Copy2Streaming();
    }

    public static void ClearAllAssetBundleName()
    {
        string[] names = AssetDatabase.GetAllAssetBundleNames();
        for (int i = 0; i < names.Length; i++)
        {
            string[] paths = AssetDatabase.GetAssetPathsFromAssetBundle(names[i]);
            for (int j = 0; j < paths.Length; j++)
            {
                var importer = AssetImporter.GetAtPath(paths[j]);
                importer.assetBundleName = string.Empty;
            }

            AssetDatabase.RemoveAssetBundleName(names[i], true);
        }

        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    public static void MarkAllAssetBundleName()
    {
        AssetDatabase.RemoveUnusedAssetBundleNames();

        foreach (var pair in DicA)
        {
            var item = pair.Value;
            AssetImporter importer = AssetImporter.GetAtPath(item.Path);
            importer.assetBundleName = item.Bundle;
            importer.assetBundleVariant = null;
        }

        AssetDatabase.Refresh();

        Debug.Log("AssetBundle 标记完成");
    }


    private static string buildPath;
    private static string workingPath;
    private static string packagePath;

    public static void BuildAssetBundle()
    {
        buildPath = Path.Combine(Application.dataPath, "../../Build/AssetBundle");
        Utils.File.CreateDirectory(buildPath);

        workingPath = Path.Combine(buildPath, $"Working/{BuildTarget.WebGL}");
        Utils.File.CreateDirectory(workingPath);

        var options = BuildAssetBundleOptions.AppendHashToAssetBundleName |
                      BuildAssetBundleOptions.ChunkBasedCompression |
                      BuildAssetBundleOptions.DisableWriteTypeTree |
                      BuildAssetBundleOptions.None;
        BuildPipeline.BuildAssetBundles(workingPath, options, BuildTarget.WebGL);

        Debug.Log("AssetBundle Build 完成");
    }

    public static void GatherBundleMd5()
    {
        var files = Utils.File.GetFiles(workingPath);
        files.RemoveAll(c => c.EndsWith(".manifest"));
        files.RemoveAll(c => c.EndsWith(".json"));

        foreach (var path in files)
        {
            var fi = new FileInfo(path);

            var index = fi.Name.LastIndexOf("_", StringComparison.Ordinal);
            if (index < 0)
            {
                continue;
            }

            var md5 = fi.Name[(index + 1)..];
            var bundle_name = fi.Name[..index];

            if (DicB.TryGetValue(bundle_name, out var bundle) == false)
            {
                continue;
            }

            bundle.MD5 = md5;
            bundle.Length = (int)fi.Length;
        }
    }

    private static void Encrypt()
    {
        packagePath = Path.Combine(buildPath, $"Package/{BuildTarget.WebGL}");
        Utils.File.CreateDirectory(packagePath);

        File.WriteAllText(Path.Combine(workingPath, "res_list.json"), GetResListJson());

        var str = File.ReadAllText(Path.Combine(workingPath, "res_list.json"));
        var encrypt_str = Utils.Crypto.Encrypt(str, "uertyewtruieywty");
        File.WriteAllText(Path.Combine(packagePath, "game.dat"), encrypt_str);

        foreach (var pair in DicB)
        {
            var bundle = pair.Value;
            var tgt_path = Path.Combine(packagePath, bundle.MD5);

            if (File.Exists(tgt_path))
            {
                File.Delete(tgt_path);
            }

            var fs = new FileStream(tgt_path, FileMode.Create);

            var src_path = Path.Combine(workingPath, $"{bundle.Name}_{bundle.MD5}");
            var bytes = File.ReadAllBytes(src_path);

            fs.Write(bytes);


            fs.Flush();
            fs.Close();
        }
    }

    private static void Copy2Streaming()
    {
        var streamingPath = Path.Combine(Application.streamingAssetsPath, "abs");
        Utils.File.CopyFolder(packagePath, streamingPath);

        AssetDatabase.Refresh();

        Debug.Log("AssetBundle Build 完成");
    }

    private static void StatisticAssets()
    {
        DicA.Clear();

        var files = Utils.File.GetFiles(Path.Combine(Application.dataPath, RES_ROOT));
        files.RemoveAll(c => c.EndsWith(".meta"));
        Debug.Log(files.Count);

        var queue = new Queue<string>();

        foreach (var file in files)
        {
            var index = file.IndexOf("Assets", StringComparison.Ordinal);
            var asset_path = file[index..];
            queue.Enqueue(asset_path);
        }

        var relies_lst = new List<string>();

        while (queue.Count > 0)
        {
            var asset_path = queue.Dequeue();
            var asset = AssetDatabase.LoadAssetAtPath(asset_path, typeof(UnityEngine.Object));
            if (asset == null)
            {
                continue;
            }

            AddRelyItem(asset_path);

            relies_lst.Clear();
            var relies = AssetDatabase.GetDependencies(asset_path);

            relies_lst.AddRange(relies);
            relies_lst.Remove(asset_path);
            relies_lst.RemoveAll(c => c.EndsWith(".cs"));

            foreach (var relies_path in relies_lst)
            {
                if (relies_path.StartsWith("Assets") == false)
                {
                    continue;
                }

                asset = AssetDatabase.LoadAssetAtPath(relies_path, typeof(UnityEngine.Object));
                if (asset == null)
                {
                    continue;
                }

                AddRelyItem(relies_path);

                SetRelyRelation(asset_path, relies_path);

                queue.Enqueue(relies_path);
            }
        }
    }

    private static void AddRelyItem(string path)
    {
        if (DicA.ContainsKey(path)) return;

        DicA[path] = new AssetItem
        {
            Path = path,
        };
    }

    private static void SetRelyRelation(string path, string rely_pah)
    {
        if (DicA.ContainsKey(path) == false || DicA.ContainsKey(rely_pah) == false)
        {
            return;
        }

        var item1 = DicA[path];
        var item2 = DicA[rely_pah];
        if (item1.Relies.Contains(rely_pah) == false)
        {
            item1.Relies.Add(rely_pah);
        }

        if (item2.BeRelies.Contains(path) == false)
        {
            item2.BeRelies.Add(path);
        }
    }

    private static string GetBundleName(AssetItem asset)
    {
        var index1 = asset.Path.LastIndexOf("/", StringComparison.Ordinal);
        var bundle_name = asset.Path[..index1];

        for (int i = 0; i < UniPath.Length; i++)
        {
            var index = bundle_name.IndexOf(UniPath[i], StringComparison.Ordinal);
            if (index >= 0)
            {
                bundle_name = UniPath[i];
                break;
            }
        }

        bundle_name = bundle_name.Replace("/", "_");
        return bundle_name;
    }

    private static bool IsUniPath(AssetItem asset)
    {
        for (int i = 0; i < UniPath.Length; i++)
        {
            var index = asset.Path.IndexOf(UniPath[i], StringComparison.Ordinal);
            if (index >= 0)
            {
                return true;
            }
        }

        return false;
    }

    private static void SetBundleName(AssetItem asset, string bundle_name)
    {
        bundle_name = bundle_name.ToLower();

        asset.Bundle = bundle_name;

        if (DicB.ContainsKey(bundle_name) == false)
        {
            DicB[bundle_name] = new BundleItem
            {
                Name = bundle_name
            };
        }

        var item = DicB[bundle_name];
        if (item.Assets.Contains(asset.Path) == false)
        {
            item.Assets.Add(asset.Path);
        }
    }

    private static void SetBundleName(AssetItem asset)
    {
        if (string.IsNullOrEmpty(asset.Bundle) == false)
        {
            return;
        }

        if (IsUniPath(asset) || asset.BeRelies.Count == 0)
        {
            SetBundleName(asset, GetBundleName(asset));
            return;
        }

        bool isSame = true;
        string bundle_name = string.Empty;
        for (int i = 0; i < asset.BeRelies.Count; i++)
        {
            var name = asset.BeRelies[i];
            var asset1 = DicA[name];
            if (string.IsNullOrEmpty(asset1.Bundle))
            {
                SetBundleName(asset1);
            }

            if (bundle_name == string.Empty)
            {
                bundle_name = asset1.Bundle;
            }
            else if (bundle_name != asset1.Bundle)
            {
                isSame = false;
                break;
            }
        }

        SetBundleName(asset, isSame ? bundle_name : GetBundleName(asset));
    }

    private static void StatisticBundles()
    {
        DicB.Clear();

        foreach (var pair in DicA)
        {
            var asset = pair.Value;
            SetBundleName(asset);
        }
    }

    private static string GetResListJson()
    {
        JsonObj obj = new JsonObj();

        foreach (var pair in DicA)
        {
            obj.Assets.Add(pair.Value);
        }

        foreach (var pair in DicB)
        {
            obj.Bundles.Add(pair.Value);
        }

        var json = JsonUtility.ToJson(obj);
        Debug.Log(json);
        return json;
    }
}